"""Assorted helpers for RDFshow internal use."""

import rdflib

__all__ = []


def html_escape(s):
    """Escape characters <, >, &, " and ' in `s` with HTML entities.
    
    Replace all occurences of HTML special characters in `s` with their
    HTML entity equivalents, and replace all occurences of the apostrophe
    (single quote) with ``&#39;`` (per XHTML 1.0 section C.16).
    """
    return s.replace(u'&', u'&amp;').replace(u'<', u'&lt;').\
             replace(u'>', u'&gt;').replace(u'"', u'&quot;').\
             replace(u"'", u'&#39;')


class AncestryTracker(object):
    """Tracker of resource inheritance along RDF Schema hierarchies.
    
    Analyzes a graph to infer inheritance structures for class instances and
    properties, as defined by ``rdf:type``, ``rdfs:subClassOf`` and
    ``rdfs:subPropertyOf``. For this to be useful, the graph must contain
    schema triples for the ontologies in question. If, however, this schema
    information is incomplete or altogether absent from the graph, the results
    of tracking will likewise be incomplete, not incorrect.
    """
    def __init__(self, graph):
        """Create an ancestry tracker for the specified `graph`."""
        self.graph = graph
    
    def _trace(self, node, ancestry, this_prop, next_prop, hops=1):
        """Recursively trace inheritance for `node`, filling up `ancestry`.
        
        `this_prop` is the property to trace along on this step, `next_prop` on
        next step. `hops` is the value to assign to ancestors found on this
        step (i.e., number of hops from the origin to this step's ancestors).
        """
        found = []
        for s, p, o in self.graph.triples((node, this_prop, None)):
            found.append(o)
        for ancestor in found:
            if ancestor not in ancestry:
                ancestry[ancestor] = hops
                self._trace(ancestor, ancestry, next_prop, next_prop, hops + 1)
    
    def get_ancestry(self, node, is_property=False):
        """Get ancestry for `node`.
        
        If `is_property` is True, assume that `node` is an RDFS property;
        otherwise, assume that it is an instance of one or more RDFS classes.
        Ancestry is returned in the form of a dictionary, where keys are nodes
        and values are the numbers of inheritance steps from `node` to the key.
        
        For properties, the first inheritance step is `node` itself, and it has
        a value of 0; parent properties have values of 1, 2 and so on. For
        class instances, the first inheritance steps are the ``rdf:type``
        objects for the instance and they have values of 1; subsequent parent
        classes traced along ``rdfs:subClassOf`` get values of 2, 3 and so on.
        """
        rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
        rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
        ancestry = {}
        if is_property:
            ancestry[node] = 0      # Begin with the property itself
            self._trace(node, ancestry, this_prop=rdfs['subPropertyOf'],
                        next_prop=rdfs['subPropertyOf'])
        else:
            self._trace(node, ancestry, this_prop=rdf['type'],
                        next_prop=rdfs['subClassOf'])
        return ancestry
